﻿#if UNITY_2019_4_OR_NEWER
#define UNITY_3D
#endif

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;

namespace Devil
{


#if !UNITY_3D

    public enum ELogLv
    {
        Assert,
        Info,
        Warning,
        Error,
    }

    public class Debug
    {
        static readonly object sync = new object();
        static readonly object sync_file = new object();
        const int LOG_BUFFER = 1024;
        static Queue<string> sLogQueue;
        static StreamWriter sLogStream;
        static ThreadFlag sLogFlag;
        static string sLogFile;
        static bool sAppendLog;
        static ELogLv sLogLv;
        static string GetFolderName(string path)
        {
            var i = path.LastIndexOf('/');
            //#if UNITY_STANDALONE_WIN
            var n = path.LastIndexOf('\\');
            if (n > i)
                i = n;
            //#endif
            return i > 0 ? path.Substring(0, i) : "./";
        }

        public static void StartLogFile(ELogLv lv, string file, bool append)
        {
            if (string.IsNullOrEmpty(file))
                return;
            lock (sync)
            {
                sLogLv = lv;
                if (sLogFlag == null || sLogFlag.IsAbort)
                {
                    if (sLogQueue == null)
                        sLogQueue = new Queue<string>();
                    sLogFile = file;
                    sAppendLog = append;
                    sLogFlag = new ThreadFlag();
                    sLogFlag.Start(WriteLogThread);
                }
            }
        }

        public static void StopLogFile()
        {
            try
            {
                if (sLogFlag != null)
                    sLogFlag.Abort();
                lock (sync_file)
                {
                    if (sLogStream != null)
                    {
                        sLogStream.Flush();
                        sLogStream.Dispose();
                        sLogStream = null;
                    }
                }
            }
            catch(Exception e)
            {
                CatchException(e);
            }
        }

        static void WriteLogThread(object state)
        {
            var flag = state as ThreadFlag;
            try
            {
                lock (sync_file)
                {
                    if (sLogStream != null)
                        sLogStream.Dispose();
                    var folder = GetFolderName(sLogFile);
                    if (!Directory.Exists(folder))
                        Directory.CreateDirectory(folder);
                    var stream = File.Open(sLogFile, sAppendLog && File.Exists(sLogFile) ? FileMode.Append : FileMode.Create);
                    sLogStream = new StreamWriter(stream);
                    if (sAppendLog)
                    {
                        sLogStream.WriteLine();
                    }
                    sLogStream.WriteLine(DateTime.Now.ToString("yyyy-MM-dd (ddd)"));
                }
            }
            catch (Exception e)
            {
                CatchException(e);
                flag.Abort();
            }
            while (flag != null && flag.IsVaild)
            {
                string log;
                lock (sync)
                {
                    log = sLogQueue.Count == 0 ? null : sLogQueue.Dequeue();
                }
                if (string.IsNullOrEmpty(log))
                {
                    try
                    {
                        lock (sync_file)
                        {
                            sLogStream.Flush();
                        }
                    }
                    catch (Exception e)
                    {
                        CatchException(e);
                    }
                    Thread.Sleep(500);
                    continue;
                }
                try
                {
                    lock (sync_file)
                    {
                        sLogStream.WriteLine(log);
                    }
                }
                catch (Exception e)
                {
                    CatchException(e);
                    flag.Abort();
                }
            }
            try
            {
                lock (sync_file)
                {
                    if (sLogStream != null)
                    {
                        sLogStream.Flush();
                        sLogStream.Dispose();
                        sLogStream = null;
                    }
                }
            }
            catch(Exception e)
            {
                CatchException(e);
            }
        }

        static void CatchException(Exception e)
        {
            var info = string.Format("[{0}] {1}", DateTime.Now.ToString("HH:mm:ss.ffff"), e);
            lock (sync)
            {
                var c = Console.ForegroundColor;
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine(info);
                Console.ForegroundColor = c;
            }
        }

        public static void LogException(Exception e) 
        {
            var info = string.Format("[E,{0}] {1}", DateTime.Now.ToString("HH:mm:ss.ffff"), e.ToString());
            lock (sync)
            {
                var c = Console.ForegroundColor;
                Console.ForegroundColor = ConsoleColor.Red;
                // (ConsoleColor)(Thread.CurrentThread.ManagedThreadId % 15 + 1);
                Console.WriteLine(info);
                Console.WriteLine();
                Console.ForegroundColor = c;
                if (sLogQueue != null && sLogQueue.Count < LOG_BUFFER)
                    sLogQueue.Enqueue(info);
            }
        }

        public static void LogFormat(string format, params object[] args)
        {
            var info = string.Format("[I,{0}] {1}", DateTime.Now.ToString("HH:mm:ss.ffff"), string.Format(format, args));
            lock (sync)
            {
                var c = Console.ForegroundColor;
                Console.ForegroundColor = ParallelDispatcher.IsMainThread ? ConsoleColor.White : ConsoleColor.DarkGray;
                //(ConsoleColor)(Thread.CurrentThread.ManagedThreadId % 15 + 1);
                Console.WriteLine(info);
                Console.WriteLine();
                Console.ForegroundColor = c;
                if (sLogQueue != null && sLogLv <= ELogLv.Info && sLogQueue.Count < LOG_BUFFER)
                    sLogQueue.Enqueue(info);
            }
        }

        public static void LogWarningFormat(string format, params object[] args)
        {
            var info = string.Format("[W,{0}] {1}", DateTime.Now.ToString("HH:mm:ss.ffff"), string.Format(format, args));
            lock (sync)
            {
                var c = Console.ForegroundColor;
                Console.ForegroundColor = ConsoleColor.Yellow;
                //(ConsoleColor)(Thread.CurrentThread.ManagedThreadId % 15 + 1);
                Console.WriteLine(info);
                Console.WriteLine();
                Console.ForegroundColor = c;
                if (sLogQueue != null && sLogLv <= ELogLv.Warning && sLogQueue.Count < LOG_BUFFER)
                    sLogQueue.Enqueue(info);
            }
        }

        public static void LogErrorFormat(string format, params object[] args)
        {
            var info = string.Format("[E,{0}] {1}", DateTime.Now.ToString("HH:mm:ss.ffff"), string.Format(format, args));
            lock (sync)
            {
                var c = Console.ForegroundColor;
                Console.ForegroundColor = ConsoleColor.Red;
                //(ConsoleColor)(Thread.CurrentThread.ManagedThreadId % 15 + 1);
                Console.WriteLine(info);
                Console.WriteLine();
                Console.ForegroundColor = c;
                if (sLogQueue != null && sLogQueue.Count < LOG_BUFFER)
                    sLogQueue.Enqueue(info);
            }
        }

        public static void Log(string info)
        {
            var log = string.Format("[I,{0}] {1}", DateTime.Now.ToString("HH:mm:ss.ffff"), info);
            lock (sync)
            {
                var c = Console.ForegroundColor;
                Console.ForegroundColor = ParallelDispatcher.IsMainThread ? ConsoleColor.White : ConsoleColor.DarkGray;
                //(ConsoleColor)(Thread.CurrentThread.ManagedThreadId % 15 + 1);
                Console.WriteLine(log);
                Console.WriteLine();
                Console.ForegroundColor = c;
                if (sLogQueue != null && sLogLv <= ELogLv.Info && sLogQueue.Count < LOG_BUFFER)
                    sLogQueue.Enqueue(info);
            }
        }

        public static void LogWarning(string info)
        {
            var log = string.Format("[W,{0}] {1}", DateTime.Now.ToString("HH:mm:ss.ffff"), info);
            lock (sync)
            {
                var c = Console.ForegroundColor;
                Console.ForegroundColor = ConsoleColor.Yellow;
                //(ConsoleColor)(Thread.CurrentThread.ManagedThreadId % 15 + 1);
                Console.WriteLine(log);
                Console.WriteLine();
                Console.ForegroundColor = c;
                if (sLogQueue != null && sLogLv <= ELogLv.Warning && sLogQueue.Count < LOG_BUFFER)
                    sLogQueue.Enqueue(info);
            }
        }
        public static void LogError(string info)
        {
            var log = string.Format("[{0}] {1}", DateTime.Now.ToString("HH:mm:ss.ffff"), info);
            lock (sync)
            {
                var c = Console.ForegroundColor;
                Console.ForegroundColor = ConsoleColor.Red;
                //(ConsoleColor)(Thread.CurrentThread.ManagedThreadId % 15 + 1);
                Console.WriteLine(log);
                Console.WriteLine();
                Console.ForegroundColor = c;
                if (sLogQueue != null && sLogQueue.Count < LOG_BUFFER)
                    sLogQueue.Enqueue(log);
            }
        }
    }
#endif
}
